Python 51.6%
TypeScript 46.7%
CSS 1.7%
1import { ImageResponse } from "next/og";2import { ENGINE_URL } from "@/lib/api";3import { PARTIES } from "@/lib/parties";45export const runtime = "nodejs";6export const alt = "QC26 — Prévision par circonscription";7export const size = { width: 1200, height: 630 };8export const contentType = "image/png";910export default async function Image({ params }: { params: Promise<{ slug: string }> }) {11 const { slug } = await params;12 let d: { name: string; regionName: string | null; forecast?: { favorite: string; p: number; parties: { party: string; voteMean: number; pWin: number }[] } } | null = null;13 try { d = await fetch(`${ENGINE_URL}/api/ridings/${slug}`, { next: { revalidate: 300 } }).then((r) => (r.ok ? r.json() : null)); } catch { d = null; }14 const color = (id: string) => PARTIES.find((p) => p.id === id)?.color ?? "#999";15 const rows = (d?.forecast?.parties ?? []).filter((p) => p.party !== "aut").slice(0, 5);16 return new ImageResponse(17 (18 <div style={{ display: "flex", width: "100%", height: "100%", flexDirection: "column", background: "#F7F8FA", color: "#111318", padding: 56, fontFamily: "Inter, sans-serif" }}>19 <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>20 <div style={{ display: "flex", alignItems: "baseline", fontSize: 40, fontWeight: 800, letterSpacing: -2 }}><span>QC</span><span style={{ width: 4, height: 32, background: "#2563EB", margin: "0 6px", borderRadius: 2 }} /><span style={{ color: "#2563EB" }}>26</span></div>21 <div style={{ display: "flex", fontSize: 18, color: "#656B76", letterSpacing: 3, textTransform: "uppercase" }}>{d?.regionName ?? "Circonscription"}</div>22 </div>23 <div style={{ display: "flex", fontSize: 64, fontWeight: 800, letterSpacing: -2, marginTop: 24 }}>{d?.name ?? slug}</div>24 <div style={{ display: "flex", fontSize: 24, color: "#656B76", marginTop: 4 }}>Probabilité de victoire · projection QC26</div>25 <div style={{ display: "flex", flexDirection: "column", marginTop: 28, flex: 1 }}>26 {rows.map((p) => (27 <div key={p.party} style={{ display: "flex", alignItems: "center", gap: 18, marginBottom: 12 }}>28 <div style={{ display: "flex", width: 90, fontSize: 28, fontWeight: 700, color: color(p.party) }}>{p.party.toUpperCase()}</div>29 <div style={{ display: "flex", flex: 1, height: 24, background: "#E9ECF0", borderRadius: 6 }}><div style={{ display: "flex", width: `${p.pWin * 100}%`, height: 24, background: color(p.party), borderRadius: 6 }} /></div>30 <div style={{ display: "flex", width: 120, fontSize: 30, fontWeight: 800, textAlign: "right" }}>{Math.round(p.pWin * 100)} %</div>31 </div>32 ))}33 </div>34 <div style={{ display: "flex", justifyContent: "space-between", fontSize: 20, color: "#656B76" }}><span>Élection québécoise · 5 octobre 2026</span><span>qc26.ca</span></div>35 </div>36 ),37 { ...size },38 );39}40